package control;

import java.util.LinkedList;
import java.util.List;

import model.Class;
import model.CompileError;
import model.Constant;
import model.ErrorType;
import model.IDType;
import model.Method;
import model.Parameter;
import model.ParseError;
import model.STContent;
import model.SemanticError;
import model.Token;
import model.Token.Value;
import model.VarType;
import model.Variable;

/**
 * Controlador responsável pela gestão Analisador Sintático Descendente
 * Preditivo Recursivo e do Analisador Semântico, essa classe utiliza como
 * "entrada" a lista de tokens provenientes do Analisador Léxico,
 * <code>ControllerLexicAnalyzer</code>, e faz o processo de validação das
 * sentenças do código-fonte e das regras semânticas. Realiza, então, vários
 * acessos à lista de tokens para utilizá-los no cumprimento de sua tarefa. A
 * análise semântica fica a cargo do método sobrecarregado
 * <code>match(Value current, VarType rightSideType)</code>.
 * 
 * @author Jody Matos, Alenilson
 * @version 2.0
 * @since 10/18/2011
 * 
 */
public class ControllerParser {
    private List<Token> tokens;
    private List<CompileError> errors;
    private Token currentToken;
    private int currentIndex;
    private Value lookahead;
    private int parseErrors;

    private LinkedList<STContent> sintaticTable;
    private LinkedList<VarType> paramList;
    private VarType leftSideType;
    private VarType returnType;
    private String currentScope;
    private String objectScope;
    private String objectType;
    private String extendsScope;
    private String localScope;
    private String idAtrib;
    private IDType currentContent;
    private boolean isReturn;

    /**
     * Construtor de <code>ControllerParser</code>, que recebe a lista de tokens
     * e a lista de erros do léxico.
     */
    public ControllerParser(List<Token> tokens, List<CompileError> errors) {
	this.tokens = tokens;
	this.lookahead = null;
	this.currentToken = null;
	this.currentIndex = 0;
	this.parseErrors = 0;
	this.errors = errors;
	sintaticTable = new LinkedList<STContent>();
	leftSideType = null;
	isReturn = false;
    }

    /**
     * Inicia o processo de análise, chamando o símbolo incial da gramática.
     */
    public void parse() {
	blocoConstGlobal();
    }

    /**
     * Faz a verificação do símbolo terminal a ser analisado.
     * 
     * @param current
     */
    private void match(Value current) {
	if (lookahead == current) {
	    if (currentIndex < tokens.size())
		lookahead = nextTerminal();
	} else {
	    parseErrors++;
	    /*
	     * ParseError error = new ParseError(currentToken.getLexeme(),
	     * ErrorType.MATCH, currentToken.getLine(), current);
	     * errors.add(error);/* recovery(follows);
	     */
	}
    }

    // match usado para verificação semântica
    private void match(Value current, VarType rightSideType) {
	// Se não houver erro sintático nesse token
	if (lookahead == current) {
	    // Se for o identificador do lado esquerdo da igualdade (soh mandar
	    // null qdo for identificador)
	    if (rightSideType == null) {
		// se for declaração local e a classe atual herdar de alguma
		// outra
		String scope2test = extendsScope == null ? currentScope
			: currentScope + "@" + extendsScope;
		// se não houver entrada igual na TS nesse escopo (no .equals tb
		// considera o global)
		if (!sintaticTable.contains(new STContent(currentToken
			.getLexeme(), scope2test))) {
		    STContent content = new STContent(currentToken.getLexeme(),
			    scope2test);
		    switch (currentContent) {
		    case CONST:
			content = leftSideType == VarType.OBJECT ? new Constant(
				content, leftSideType, objectType)
				: new Constant(content, leftSideType);
			objectType = null;
			break;
		    case VAR:
			content = leftSideType == VarType.OBJECT ? new Variable(
				content, leftSideType, objectType)
				: new Variable(content, leftSideType);
			objectType = null;
			break;
		    case CLASS:
			currentScope = currentToken.getLexeme();
			content = new Class(content);
			// content.setScope(currentScope);
			break;
		    case METH:

			break;
		    case PARAM:
			content = new Parameter(content, leftSideType);
			content.setScope(localScope + "@" + currentScope);
			break;
		    }
		    sintaticTable.push(content);
		} else {
		    // TODO verificar os casos de sobrescrita de método
		    SemanticError error = new SemanticError(
			    currentToken.getLexeme(),
			    ErrorType.IDENTIFIER_REDECLARED,
			    currentToken.getLine());
		    errors.add(error);
		}
	    }
	    // Se não for o identificador do lado esquerdo
	    else {
		// Se tiver identificador do lado direito, preparar rightSide
		if (rightSideType == VarType.IDENTIFIER) {
		    String scope2test = objectScope == null ? currentScope
			    : objectScope;
		    if (sintaticTable.contains(new STContent(currentToken
			    .getLexeme(), scope2test))) {
			int index = sintaticTable.indexOf(new STContent(
				currentToken.getLexeme(), scope2test));
			STContent content = sintaticTable.get(index);
			switch (content.getContent()) {
			case CONST:
			case VAR:
			    // Variable var = (Variable) content; (trecho Jody)
			    // //dá erro de cast de classe
			    // abaixo a solucao parcial para o problema, mas já
			    // roda os testes 5,6, 7 e 11
			    // falta agora saber setar o segundo parametro em
			    // new Variable
			    // (content, null)
			    // tentei pegar do currentToken mas os tipos enums
			    // são incompatíveis
			    Variable var = new Variable(content, null); // (trecho
									// Alenilson)

			    rightSideType = var.getType();
			    objectScope = null;
			    break;
			case CLASS:
			    // TODO fazer reconhecer o escopo de uma classe pai
			    objectScope = currentToken.getLexeme();
			    rightSideType = leftSideType; // só pra não dar erro
							  // semântico
			    break;
			case METH:
			    Method meth = (Method) content;
			    rightSideType = meth.getRetorno();
			    objectScope = null;
			    break;
			case PARAM:
			    Parameter param = (Parameter) content;
			    rightSideType = param.getType();
			    break;
			}
		    } else {
			SemanticError error = new SemanticError(
				currentToken.getLexeme(),
				ErrorType.IDENTIFIER_MISMATCH,
				currentToken.getLine());
			errors.add(error);
		    }
		} else if (rightSideType == VarType.OBJECT) {
		    String scope2test = objectScope == null ? currentScope
			    : objectScope;
		    if (sintaticTable.contains(new STContent(currentToken
			    .getLexeme(), scope2test))) {
			leftSideType = VarType.OBJECT;
			objectType = currentToken.getLexeme();
		    } else {
			SemanticError error = new SemanticError(
				currentToken.getLexeme(),
				ErrorType.IDENTIFIER_MISMATCH,
				currentToken.getLine());
			errors.add(error);
			leftSideType = null;
			rightSideType = null;
		    }
		}
		// Com o rightSide garantidamente preparado, fazer verificação
		// de tipo
		if (leftSideType != rightSideType) {
		    if (rightSideType != returnType) {
			if (!isReturn) {
			    // leftSideType =
			    // leftSideType==null?returnType:leftSideType;
			    SemanticError error = new SemanticError(
				    currentToken.getLexeme(),
				    ErrorType.TYPE_MISMATCH,
				    currentToken.getLine(), leftSideType,
				    rightSideType);
			    errors.add(error);
			} else {
			    System.out.println("returnType:" + returnType);
			    SemanticError error = new SemanticError(
				    currentToken.getLexeme(),
				    ErrorType.TYPE_MISMATCH,
				    currentToken.getLine(), returnType,
				    rightSideType);
			    errors.add(error);
			}
		    }
		}
	    }
	    if (currentIndex < tokens.size())
		lookahead = nextTerminal();
	} else {
	    parseErrors++;
	    /*
	     * ParseError error = new ParseError(currentToken.getLexeme(),
	     * ErrorType.MATCH, currentToken.getLine(), current);
	     * errors.add(error);/* recovery(follows);
	     */
	}
    }

    /**
     * Retorna o próximo terminal na lista de entradas.
     * 
     * @return
     */
    private Value nextTerminal() {
	currentToken = tokens.get(currentIndex++);
	return currentToken.getValue();
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void blocoConstGlobal(Value... synchset) {
	currentScope = "global";

	int prevParseErrors = parseErrors;
	lookahead = nextTerminal();
	switch (lookahead) {
	case CONSTANTS:
	    match(Value.CONSTANTS);
	    match(Value.OPENKEY);
	    decConst(Value.CLOSEKEY);
	    match(Value.CLOSEKEY);
	    blocoVarGlobal();
	    break;
	case VARIABLES:
	    match(Value.VARIABLES);
	    match(Value.OPENKEY);
	    decVar(Value.CLOSEKEY);
	    match(Value.CLOSEKEY);
	    blocoClasse();
	    break;
	case CLASS:
	    blocoClasse();
	    break;
	default:
	    parseErrors++;
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.GLOBAL_CONST, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    break;
	}
	if (prevParseErrors != parseErrors) {
	    recovery(synchset);
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.GLOBAL_CONST, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void blocoVarGlobal(Value... synchset) {
	int prevParseErrors = parseErrors;
	switch (lookahead) {
	case VARIABLES:
	    match(Value.VARIABLES);
	    match(Value.OPENKEY);
	    decVar();
	    match(Value.CLOSEKEY);
	    blocoClasse();
	    break;
	case CLASS:
	    /*
	     * match(Value.CLASS); match(Value.IDENTIFIER); heranca();
	     * match(Value.OPENKEY); blocoConstAtrib(); match(Value.CLOSEKEY);
	     */
	    blocoClasse();
	    break;
	default:
	    parseErrors++;
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.GLOBAL_VAR, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    break;
	}
	if (prevParseErrors != parseErrors) {
	    recovery(synchset);
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.GLOBAL_VAR, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void blocoClasse(Value... synchset) {
	currentContent = IDType.CLASS;

	int prevParseErrors = parseErrors;
	switch (lookahead) {
	case CLASS:
	    match(Value.CLASS);
	    match(Value.IDENTIFIER, null);
	    heranca();
	    match(Value.OPENKEY);
	    blocoConstAtrib();
	    match(Value.CLOSEKEY);
	    blocoClasseAux();
	    break;
	default:
	    parseErrors++;
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.DEC_CLASS, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    break;
	}
	if (prevParseErrors != parseErrors) {
	    recovery(synchset);
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.DEC_CLASS, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void blocoClasseAux() {
	switch (lookahead) {
	case CLASS:
	    blocoClasse();
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void heranca() {
	switch (lookahead) {
	case EXTENDS:
	    // TODO fazer implementação para atribuição de herança
	    match(Value.EXTENDS);
	    match(Value.IDENTIFIER);
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void blocoConstAtrib() {
	switch (lookahead) {
	case CONSTANTS:
	    match(Value.CONSTANTS);
	    match(Value.OPENKEY);
	    decConst(Value.CLOSEKEY);
	    match(Value.CLOSEKEY);
	    blocoVarAtrib();
	    break;
	case VARIABLES:
	case METHODS:
	    blocoVarAtrib();
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void blocoVarAtrib() {
	switch (lookahead) {
	case VARIABLES:
	    match(Value.VARIABLES);
	    match(Value.OPENKEY);
	    decVar();
	    match(Value.CLOSEKEY);
	    blocoMetodos();
	    break;
	case METHODS:
	    match(Value.METHODS);
	    match(Value.OPENKEY);
	    decMetodos(Value.CLOSEKEY);
	    match(Value.CLOSEKEY);
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void blocoMetodos() {
	switch (lookahead) {
	case METHODS:
	    match(Value.METHODS);
	    match(Value.OPENKEY);
	    decMetodos(Value.CLOSEKEY);
	    match(Value.CLOSEKEY);
	    break;
	default:
	    // ver derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void decConst(Value... synchset) {
	currentContent = IDType.CONST;

	int prevParseErrors = parseErrors;
	switch (lookahead) {
	// caso do lado direito começando por um nao-terminal,utilizar cjto
	// primeiro
	case INT:
	case FLOAT:
	case BOOL:
	case STRING:
	case CHAR:
	    tipo();
	    A();
	    match(Value.SEMICOLON);
	    decConst();
	    break;
	default:
	    // derivacao vazia
	}
	if (prevParseErrors == parseErrors) {

	} else {
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.DEC_CONST, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    recovery(synchset);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void tipo() {
	switch (lookahead) {
	case INT:
	    match(Value.INT);
	    leftSideType = VarType.INT;
	    break;
	case FLOAT:
	    match(Value.FLOAT);
	    leftSideType = VarType.FLOAT;
	    break;
	case BOOL:
	    match(Value.BOOL);
	    leftSideType = VarType.BOOL;
	    break;
	case STRING:
	    match(Value.STRING);
	    leftSideType = VarType.STRING;
	    break;
	case CHAR:
	    match(Value.CHAR);
	    leftSideType = VarType.CHAR;
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void A() {
	switch (lookahead) {
	case IDENTIFIER:
	    match(Value.IDENTIFIER, null);
	    match(Value.EQ);
	    valor();
	    auxA();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxA() {
	switch (lookahead) {
	case COMMA:
	    match(Value.COMMA);
	    A();
	    break;
	default:
	    // derivacao vazia
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void decVar(Value... synchset) {
	currentContent = IDType.VAR;

	int prevParseErrors = parseErrors;
	switch (lookahead) {
	// caso do lado direito começando por um nao-terminal,utilizar cjto
	// primeiro
	case INT:
	case FLOAT:
	case BOOL:
	case STRING:
	case CHAR:
	    tipo();
	    B();
	    match(Value.SEMICOLON);
	    decVar();
	    break;
	case IDENTIFIER:
	    match(Value.IDENTIFIER, VarType.OBJECT);
	    bObj();
	    match(Value.SEMICOLON);
	    decVar();
	default:
	    // derivacao vazia
	}
	if (prevParseErrors != parseErrors) {
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.DEC_VAR, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    recovery(synchset);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void bObj() {
	switch (lookahead) {
	case IDENTIFIER:
	    match(Value.IDENTIFIER, null);
	    bObjAux();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void bObjAux() {
	switch (lookahead) {
	case COMMA:
	    match(Value.COMMA);
	    bObj();
	    break;
	case /* EQ */OPENPAR:
	    // TODO fazer implementação para verificação de método
	    /* match(Value.EQ); */match(Value.OPENPAR);
	    valorObj();
	    match(Value.CLOSEPAR);
	    pObj();
	    break;
	// TODO fazer implementação pra inicialização de variável
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void pObj() {
	switch (lookahead) {
	case COMMA:
	    match(Value.COMMA);
	    bObj();
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void valorObj() {
	switch (lookahead) {
	case STRING:
	case CHAR:
	case FALSE:
	case TRUE:
	case NULL:
	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    valor();
	    valorObjAux();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void valorObjAux() {
	switch (lookahead) {
	case COMMA:
	    match(Value.COMMA);
	    valorObj();
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void B() {
	switch (lookahead) {
	case IDENTIFIER:
	    match(Value.IDENTIFIER, null);
	    auxB();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxB() {
	switch (lookahead) {
	case COMMA:
	    match(Value.COMMA);
	    B();
	    break;
	case EQ:
	    match(Value.EQ);
	    valor();
	    auxB1();
	    break;
	case OPENBRA:
	    match(Value.OPENBRA);
	    expArit(Value.CLOSEBRA);
	    match(Value.CLOSEBRA);
	    auxB1();
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxB1() {
	switch (lookahead) {
	case COMMA:
	    match(Value.COMMA);
	    B();
	    break;
	default:
	    // derivacao vazio.
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void decMetodos(Value... synchset) {
	currentContent = IDType.METH;

	int prevParseErrors = parseErrors;
	switch (lookahead) {
	case INT:
	case FLOAT:
	case BOOL:
	case STRING:
	case CHAR:
	case NULL:
	    tipoRetorno();
	    auxMetodo();
	    match(Value.OPENPAR);
	    paramPrincipal();
	    match(Value.CLOSEPAR);
	    match(Value.OPENKEY);
	    blocoVarLocais();
	    match(Value.RETURN);
	    match(Value.OPENPAR);
	    isReturn = true;
	    valor();
	    isReturn = true;
	    match(Value.CLOSEPAR);
	    match(Value.SEMICOLON);
	    match(Value.CLOSEKEY);
	    decMetodos();
	    break;
	default:
	    // derivacao vazia
	}
	if (prevParseErrors != parseErrors) {
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.DEC_MET, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    recovery(synchset);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxMetodo() {
	switch (lookahead) {
	case IDENTIFIER:
	    localScope = currentToken.getLexeme();
	    match(Value.IDENTIFIER);
	    break;
	case MAIN:
	    localScope = currentToken.getLexeme();
	    match(Value.MAIN);
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void tipoRetorno() {
	switch (lookahead) {
	case INT:
	    match(Value.INT);
	    returnType = VarType.INT;
	    break;
	case FLOAT:
	    match(Value.FLOAT);
	    returnType = VarType.FLOAT;
	    break;
	case BOOL:
	    match(Value.BOOL);
	    returnType = VarType.BOOL;
	    break;
	case STRING:
	    match(Value.STRING);
	    returnType = VarType.STRING;
	    break;
	case CHAR:
	    match(Value.CHAR);
	    returnType = VarType.CHAR;
	    break;
	case NULL:
	    match(Value.NULL);
	    returnType = VarType.NULL;
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void paramPrincipal() {
	currentContent = IDType.PARAM;
	paramList = new LinkedList<VarType>();

	switch (lookahead) {
	case INT:
	case FLOAT:
	case BOOL:
	case STRING:
	case CHAR:
	    c();
	    break;
	case NULL:
	    match(Value.NULL);
	    paramList.add(VarType.NULL);
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void c() {
	switch (lookahead) {
	case INT:
	    match(Value.INT);
	    paramList.add(VarType.INT);
	    leftSideType = VarType.INT;
	    d();
	    auxC();
	    break;
	case FLOAT:
	    match(Value.FLOAT);
	    paramList.add(VarType.FLOAT);
	    leftSideType = VarType.FLOAT;
	    d();
	    auxC();
	    break;
	case BOOL:
	    match(Value.BOOL);
	    paramList.add(VarType.BOOL);
	    leftSideType = VarType.BOOL;
	    d();
	    auxC();
	    break;
	case STRING:
	    match(Value.STRING);
	    paramList.add(VarType.STRING);
	    leftSideType = VarType.STRING;
	    d();
	    auxC();
	    break;
	case CHAR:
	    match(Value.CHAR);
	    paramList.add(VarType.CHAR);
	    leftSideType = VarType.CHAR;
	    d();
	    auxC();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxC() {
	switch (lookahead) {
	case SEMICOLON:
	    match(Value.SEMICOLON);
	    c();
	    break;
	default:
	    // derivação vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void d() {
	switch (lookahead) {
	case IDENTIFIER:
	    match(Value.IDENTIFIER, null);
	    auxD();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxD() {
	switch (lookahead) {
	case COMMA:
	    match(Value.COMMA);
	    d();
	    break;
	case OPENBRA:
	    vet2();
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void vet2() {
	switch (lookahead) {
	case OPENBRA:
	    match(Value.OPENBRA);
	    match(Value.CLOSEBRA);
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void blocoVarLocais() {
	sintaticTable.add(new Method(new STContent(localScope, currentScope),
		paramList, returnType));
	currentScope = localScope + "@" + currentScope;
	currentContent = IDType.VAR;

	switch (lookahead) {
	case VARIABLES:
	    match(Value.VARIABLES);
	    match(Value.OPENKEY);
	    decVar(Value.CLOSEKEY);
	    match(Value.CLOSEKEY);
	    conteudo();
	    break;
	case IF:
	case FOR:
	case WHILE:
	case IDENTIFIER:
	case READ:
	case PRINT:
	case INC:
	case DEC:
	    conteudo();
	    break;
	default:
	    // derivação vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void conteudo() {
	switch (lookahead) {
	case IF:
	case FOR:
	case WHILE:
	case IDENTIFIER:
	case READ:
	case PRINT:
	case INC:
	case DEC:
	    funcoes(Value.IF, Value.FOR, Value.WHILE, Value.IDENTIFIER,
		    Value.READ, Value.PRINT, Value.INC, Value.DEC);
	    conteudo();
	    break;
	default:
	    // deriva vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void funcoes(Value... synchset) {
	int prevParseErrors = parseErrors;
	switch (lookahead) {
	case IF:
	    match(Value.IF);
	    match(Value.OPENPAR);
	    expLog(Value.CLOSEPAR);
	    match(Value.CLOSEPAR);
	    match(Value.OPENKEY);
	    conteudo();
	    retornoG();
	    match(Value.CLOSEKEY);
	    negacao();
	    break;
	case FOR:
	    match(Value.FOR);
	    match(Value.OPENPAR);
	    atribuicao(Value.SEMICOLON);
	    match(Value.SEMICOLON);
	    expLog(Value.SEMICOLON);
	    match(Value.SEMICOLON);
	    atribuicao(Value.CLOSEPAR);
	    match(Value.CLOSEPAR);
	    match(Value.OPENKEY);
	    conteudo();
	    retornoG();
	    match(Value.CLOSEKEY);
	    break;
	case WHILE:
	    match(Value.WHILE);
	    match(Value.OPENPAR);
	    expLog(Value.CLOSEPAR);
	    match(Value.CLOSEPAR);
	    match(Value.OPENKEY);
	    conteudo();
	    retornoG();
	    match(Value.CLOSEKEY);
	    break;
	case IDENTIFIER:
	    idAtrib = currentToken.getLexeme();
	    match(Value.IDENTIFIER);
	    auxFunc();
	    break;
	case READ:
	    match(Value.READ);
	    match(Value.OPENPAR);
	    fatorLeia();
	    match(Value.CLOSEPAR);
	    match(Value.SEMICOLON);
	    break;
	case PRINT:
	    match(Value.PRINT);
	    match(Value.OPENPAR);
	    fatorEsc();
	    match(Value.CLOSEPAR);
	    match(Value.SEMICOLON);
	    break;
	case INC:
	    match(Value.INC);
	    match(Value.IDENTIFIER);
	    match(Value.SEMICOLON);
	    break;
	case DEC:
	    match(Value.DEC);
	    match(Value.IDENTIFIER);
	    match(Value.SEMICOLON);
	    break;
	default:
	    parseErrors++;
	}
	if (prevParseErrors != parseErrors) {
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.FUNCTIONS, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    recovery(synchset);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void retornoG() {
	switch (lookahead) {
	case RETURN:
	    isReturn = true;
	    match(Value.RETURN);
	    match(Value.OPENPAR);
	    valor();
	    match(Value.CLOSEPAR);
	    match(Value.SEMICOLON);
	    isReturn = false;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxFunc() {
	switch (lookahead) {
	case OPENBRA:
	    vet();
	    match(Value.EQ);
	    valor();
	    match(Value.SEMICOLON);
	    break;
	case OPENPAR:
	    match(Value.OPENPAR);
	    f(Value.CLOSEPAR);
	    match(Value.CLOSEPAR);
	    match(Value.SEMICOLON);
	    break;
	case EQ:
	    match(Value.EQ);
	    valor();
	    match(Value.SEMICOLON);
	    break;
	case DOT:
	    match(Value.DOT);
	    match(Value.IDENTIFIER);
	    funcoesAux2();
	    break;
	case INC:
	    match(Value.INC);
	    match(Value.SEMICOLON);
	    break;
	case DEC:
	    match(Value.DEC);
	    match(Value.SEMICOLON);
	    break;
	default:
	    parseErrors++;
	    /*
	     * default: //derivação vazio.
	     */
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void funcoesAux2() {
	switch (lookahead) {
	case OPENPAR:
	    match(Value.OPENPAR);
	    f(Value.CLOSEPAR);
	    match(Value.CLOSEPAR);
	    match(Value.SEMICOLON);
	    break;
	case EQ:
	    match(Value.EQ);
	    valor();
	    match(Value.SEMICOLON);
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void negacao() {
	switch (lookahead) {
	case ELSE:
	    match(Value.ELSE);
	    match(Value.OPENKEY);
	    conteudo();
	    retornoG();
	    match(Value.CLOSEKEY);
	    break;
	default:
	    // derivação vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void fatorLeia() {
	switch (lookahead) {
	case IDENTIFIER:
	    match(Value.IDENTIFIER);
	    auxLeia();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxLeia() {
	switch (lookahead) {
	case COMMA:
	    match(Value.COMMA);
	    fatorLeia();
	    break;
	case OPENBRA:
	    vet();
	    break;
	default:
	    // derivação vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void fatorEsc() {
	switch (lookahead) {
	case STRING:
	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    fatorAux();
	    auxEsc();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxEsc() {
	switch (lookahead) {
	case COMMA:
	    match(Value.COMMA);
	    fatorEsc();
	    break;
	case OPENBRA:
	    vet();
	    break;
	default:
	    // derivação vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void fatorAux() {
	switch (lookahead) {
	case STRING:
	    match(Value.STRING);
	    break;
	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    expArit();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void atribuicao(Value... synchset) {
	int prevParseErrors = parseErrors;
	switch (lookahead) {
	case IDENTIFIER:
	    match(Value.IDENTIFIER, VarType.IDENTIFIER);
	    atribAux();
	    break;
	case INC:
	    match(Value.INC);
	    match(Value.IDENTIFIER, VarType.IDENTIFIER);
	    break;
	case DEC:
	    match(Value.DEC);
	    match(Value.IDENTIFIER, VarType.IDENTIFIER);
	    break;
	default:
	    parseErrors++;
	}
	if (prevParseErrors != parseErrors) {
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.ATRB, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    recovery(synchset);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void atribAux() {
	switch (lookahead) {
	case OPENBRA:
	    vet();
	    match(Value.EQ);
	    valor();
	    break;
	case EQ:
	    match(Value.EQ);
	    valor();
	    break;
	case INC:
	    match(Value.INC);
	    break;
	case DEC:
	    match(Value.DEC);
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void vet() {
	switch (lookahead) {
	case OPENBRA:
	    match(Value.OPENBRA);
	    expArit(Value.CLOSEBRA);
	    match(Value.CLOSEBRA);
	    break;
	default:
	    // derivação vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void f(Value... synchset) {
	int prevParseErrors = parseErrors;
	switch (lookahead) {
	case STRING:
	case CHAR:
	case FALSE:
	case TRUE:
	case NULL:
	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    parametros();
	    auxF();
	    break;
	default:
	    parseErrors++;
	    /*
	     * default: //derivação vazia.
	     */
	}
	if (prevParseErrors != parseErrors) {
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.MET_CALL, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    recovery(synchset);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxF() {
	switch (lookahead) {
	case SEMICOLON:
	    match(Value.SEMICOLON);
	    f();
	    break;
	default:
	    // derivação vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void parametros() {
	switch (lookahead) {
	case STRING:
	case CHAR:
	case FALSE:
	case TRUE:
	case NULL:
	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    valor();
	    auxPar();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxPar() {
	switch (lookahead) {
	case COMMA:
	    match(Value.COMMA);
	    parametros();
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void valor() {
	switch (lookahead) {
	case STRING:
	    match(Value.STRING, VarType.STRING);
	    break;
	case CHAR:
	    match(Value.CHAR, VarType.CHAR);
	    break;
	case TRUE:
	    match(Value.TRUE, VarType.BOOL);
	    break;
	case FALSE:
	    match(Value.FALSE, VarType.BOOL);
	    break;

	// TODO implementar o restante de valores
	case NULL:
	    match(Value.NULL, VarType.NULL);
	    break;

	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    expLog();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void expRel(Value... synchset) {
	int prevParseErrors = parseErrors;
	switch (lookahead) {
	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    expArit(Value.HIGH, Value.HEQ, Value.LESS, Value.LEQ, Value.DIF,
		    Value.ISEQUAL);
	    auxRel();
	    break;
	default:
	    parseErrors++;
	}
	if (prevParseErrors != parseErrors) {
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.EXP_REL, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    recovery(synchset);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxRel() {
	switch (lookahead) {
	case HIGH:
	case HEQ:
	case LESS:
	case LEQ:
	case DIF:
	case ISEQUAL:
	    opRel();
	    expArit();
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void opRel() {
	switch (lookahead) {
	case HIGH:
	    match(Value.HIGH);
	    break;
	case HEQ:
	    match(Value.HEQ);
	    break;
	case LESS:
	    match(Value.LESS);
	    break;
	case LEQ:
	    match(Value.LEQ);
	    break;
	case DIF:
	    match(Value.DIF);
	    break;
	case ISEQUAL:
	    match(Value.ISEQUAL); // para ==
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void expLog(Value... synchset) {
	// TODO fazer implementação pra uma expRel sempre retornar um valor
	// logico
	int prevParseErrors = parseErrors;
	switch (lookahead) {
	case FALSE:
	case TRUE:
	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    termLog();
	    expLogR();
	    break;
	default:
	    parseErrors++;
	}
	if (prevParseErrors != parseErrors) {
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.EXP_LOG, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    recovery(synchset);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void expLogR() {
	switch (lookahead) {
	case AND:
	    match(Value.AND); /* match(Value.AND); */
	    auxLog(); /* match(Value.OR); */
	    break;
	case OR:
	    match(Value.OR);
	    auxLog();
	    break;
	default:
	    // derivaçao vazio.
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void opLog() {
	switch (lookahead) {
	case AND:
	    match(Value.AND);
	    break;
	case OR:
	    match(Value.OR);
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxLog() {
	switch (lookahead) {
	case FALSE:
	case TRUE:
	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    termLog();
	    auxLog2();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void auxLog2() {
	switch (lookahead) {
	case AND:
	case OR:
	    opLog();
	    auxLog();
	    break;
	default:
	    // derivacao vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void termLog() {
	switch (lookahead) {
	case FALSE:
	    match(Value.FALSE, VarType.BOOL);
	    break;
	case TRUE:
	    match(Value.TRUE, VarType.BOOL);
	    break;
	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    expRel();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void expArit(Value... synchset) {
	int prevParseErrors = parseErrors;
	switch (lookahead) {
	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    term();
	    exp1();
	    break;
	default:
	    parseErrors++;
	}
	if (prevParseErrors != parseErrors) {
	    ParseError error = new ParseError(currentToken.getLexeme(),
		    ErrorType.EXP_ARIT, currentToken.getLine(), Value.NO);
	    errors.add(error);
	    recovery(synchset);
	    parseErrors = 0;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void exp1() {
	switch (lookahead) {
	case MINUS:
	    match(Value.MINUS);
	    term();
	    exp1();
	    break;
	case PLUS:
	    match(Value.PLUS);
	    term();
	    exp1();
	    break;
	default:
	    // derivação vazio
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void term() {
	switch (lookahead) {
	case IDENTIFIER:
	case NUM_FLOAT:
	case NUM_INT:
	case OPENPAR:
	case INC:
	case DEC:
	    fator();
	    term1();
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void term1() {
	switch (lookahead) {
	case MULT:
	    match(Value.MULT);
	    fator();
	    term1();
	    break;
	case DIV:
	    match(Value.DIV);
	    fator();
	    term1();
	    break;
	default:
	    // derivação vazio.
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void fator() {
	switch (lookahead) {
	case IDENTIFIER:
	    match(Value.IDENTIFIER, VarType.IDENTIFIER);
	    fator1();
	    break;
	case NUM_FLOAT:
	    match(Value.NUM_FLOAT, VarType.FLOAT);
	    break;
	case NUM_INT:
	    match(Value.NUM_INT, VarType.INT);
	    break;
	case OPENPAR:
	    match(Value.OPENPAR);
	    expArit(Value.CLOSEPAR);
	    match(Value.CLOSEPAR);
	    break;
	case INC:
	    match(Value.INC);
	    match(Value.IDENTIFIER, VarType.IDENTIFIER);
	    break;
	case DEC:
	    match(Value.DEC);
	    match(Value.IDENTIFIER, VarType.IDENTIFIER);
	    break;
	default:
	    parseErrors++;
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void fator1() {
	switch (lookahead) {
	case DOT:
	    // TODO implementar lógica pra verificação de tipo de atributos de
	    // outras classes e retorno de métodos (locais e de fora);
	    match(Value.DOT);
	    match(Value.IDENTIFIER, VarType.IDENTIFIER);
	    fator2();
	    break;
	case OPENPAR:
	    match(Value.OPENPAR);
	    f(Value.CLOSEPAR);
	    match(Value.CLOSEPAR);
	    break;
	case OPENBRA:
	    match(Value.OPENBRA);
	    expArit(Value.CLOSEBRA);
	    match(Value.CLOSEBRA);
	    break;
	case INC:
	    match(Value.INC);
	    break;
	case DEC:
	    match(Value.DEC);
	    break;
	default:
	    // derivação vazio.
	}
    }

    /**
     * Método de implementação do símbolo não-terminal com mesmo nome.
     * 
     * @param synchset
     */
    private void fator2() {
	switch (lookahead) {
	case OPENPAR:
	    match(Value.OPENPAR);
	    f(Value.CLOSEPAR);
	    match(Value.CLOSEPAR);
	    break;
	default:
	    // derivação vazio.
	}
    }

    /**
     * Implementação do método de recuperação com Modo Pânico.
     * 
     * @param synchronism
     */
    private void recovery(Value... synchronism) {
	for (; currentIndex < tokens.size(); currentIndex++) {
	    for (int pos = 0; pos < synchronism.length; pos++) {
		if (synchronism[pos] == tokens.get(currentIndex).getValue())
		    return;
	    }
	}
    }

    public int getParseErrors() {
	return this.parseErrors;
    }

    public List<CompileError> getErrors() {
	return this.errors;
    }
    // fim da classe controllerParser
}
